<?php
class Dom extends DOMDocument {

    public function __construct($version = NULL, $encoding = NULL) {
        parent::__construct($version, $encoding);
    }

    /*  getXPath(string $path)  :   Gets a nodelist from specified path
     *
     *  @access public
     *  @param  string  $path       -   The path to match against
     *  @return mixed   $nodes      -   Returns Node list of matched paths, or false if nothing matched
     */
    public function getXPath($path) {
        $XPath  =   new DOMXPath($this);
        $nodes  =   $XPath->query($path);

        if(!empty($nodes)) {
            return $nodes;
        } else {
            return FALSE;
        }
    }

    public function buildPath($array) {
        switch(count($array)) {
            case "3":
                $path   =   "//". $array[0] ."[@". $array[1] ."= '". $array[2] ."']";
            break;

            case "1":
                $path   =   $array[0];
            break;

            default:
                return FALSE;
        }

        return $path;
    }

    /*  getPathFirst(string $tagName, string $attribute, string $attribValue)   :   Gets the first node that matches specified path
     *
     *  @access public
     *  @param  string  $tagName        -   Name of the tag to match against
     *  @param  string  $attribute      -   Attribute to match against
     *  @param  string  $attribValue    -   Value of the matched attribute to match against
     *  @return mixed   $node           -   Returns the node if matched, False if no match
     */
    public function getPathFirst() {
        if(($path = self::buildPath(func_get_args())) !== FALSE) {
            return self::getXPath($path)->item(0);
        } else {
            return FALSE;
        }
    }

    /*  getPathLast(string $tagName, string $attribute, string $attribValue)    :   Returns the last node that matches specified path
     *
     *  @access public
     *  @param  string  $tagName        -   Name of the tag to match against
     *  @param  string  $attribute      -   Attribute to match against
     *  @param  string  $attribValue    -   Value of the attribute to match against
     *  @return mixed   $node           -   Returns the matched node, or False if nothing found
     */
    public function getPathLast($tagName, $attribute, $attribValue) {
        if(($path = self::buildPath(func_get_args())) !== FALSE) {
            $nodeList   =   self::getXPath($path);
            $curNum     =   $nodeList->length - 1;
            $curItem    =   $nodeList->item($curNum);

            if(!empty($curItem)) {
                return $curItem;
            } else {
                return FALSE;
            }
        } else {
            return FALSE;
        }
    }

    /*  getTags(string $tagName)    :   Gets a node list on matched tag names
     *
     *  @access public
     *  @param  string  $tagName        -   Name of the tag to match against
     *  @return mixed   $nodeList       -   Returns a node list of matched tags, or false if no tags matched
     */
    public function getTags($tagName) {
        $nodeList   =   self::getElementsByTagName($tagName);

        if($nodeList->length > '0') {
            return $nodeList;
        } else {
            return FALSE;
        }
    }

    /*  getTagFirst(string $tagName)    :   Gets the first node to match tag name
     *
     *  @access public
     *  @param  string  $tagName        -   Name of the tag to match against
     *  @return mixed   $node           -   Returns the matched node, or false if no tag found
     */
    public function getTagFirst($tagName) {
        if(($nodeList = self::getTags($tagName)) !== FALSE) {
            return $nodeList->item(0);
        } else {
            return FALSE;
        }
    }

    /*  getTagLast(string $tagName)     :   Get the last node to match the tag name
     *
     *  @access public
     *  @param  string  $tagName        -   Name of the tag to match against
     *  @return mixed   $node           -   Returns the node of the matched tag, or false if no tag found
     */
    public function getTagLast($tagName) {
        if(($nodeList = self::getTags($tagName)) !== FALSE) {
            $cur    =   $nodeList->length - 1;

            return $nodeList->item($cur);
        } else {
            return FALSE;
        }
    }

    /*  setAttributes(node $targetNode, [array $attribs])       :   Sets an array of attributes to provided node
     *
     *  @access public
     *  @param  node    $targetNode     -   Target node to apply attributes to
     *  @param  array   $attribs        -   An array of attributes to apply to target node
     */
    public function setAttributes($targetNode, $attribs = NULL) {
        if(isset($attribs) && !empty($attribs)) {
            if(is_array($attribs)) {
                foreach($attribs as $attrib => $value) {
                    $targetNode->setAttribute($attrib, $value);
                }
            } else {
                throw new Exception("setAttributes expects second argument to be an array");
            }
        }
    }

    /*  appendString(DOMNode $tagetNode, string $string)        :   Appends provided string into current document
    *
    *  @access public
    *  @param  string  $string         -   The string to append to the document
    */
    public function appendString($targetNode, $string) {
    	$doc				=	new Dom();
    	
    	if(!empty($string)) {
        	$string	=	mb_convert_encoding($string, 'HTML-ENTITIES', "UTF-8");
    		if($doc->loadHTML($string) !== FALSE) {
    			if($tag = $doc->getTagFirst("body")) {
        			foreach($tag->childNodes as $childNode) {
            			$curTag	=	$targetNode->ownerDocument->importNode($childNode, TRUE);
            			$targetNode->appendChild($curTag);
            		}
    			} else {
    				print $string;
    			}
    		} else {
    			print $string;
    		}
    	}
    }

    /*  appendFile(DOMNode $targetNode, string $fileName)        :   Appends an external file into the specified node
    *
    *  @access public
    *  @param  string  $fileName       -   File to use to append
    *  @return mixed                   -   Returns false if failed to load file
    */
    public function appendFile($targetNode, $fileName) { 
       if(!file_exists($fileName) || self::appendString($targetNode, file_get_contents($fileName)) === FALSE) {
           return FALSE;
       }
    }

    /*  cleanAmp(string $data)  :   Convert '&' characters to '&amp;'
     *
     *  @access public
     *  @param  string  $data               -   The data to preform conersion on
     *  @return mixed   $data               -   Returns false if failed, and converted string if successful
     */
    public function cleanAmp($data) {
        $data   =   str_replace("&", "&amp;", $data);

        return $data;
    }
}
?>